use crate::error::AppError;
use crate::models::user::User;
use jwt_simple::prelude::*;
use std::ops::Deref;

const JWT_DURATION: u64 = 60 * 60 * 24 * 7;

#[derive(Clone)]
pub struct EncodingKey(Ed25519KeyPair);

#[derive(Clone)]
pub struct DecodingKey(Ed25519PublicKey);

impl Deref for EncodingKey {
    type Target = Ed25519KeyPair;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl Deref for DecodingKey {
    type Target = Ed25519PublicKey;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
impl EncodingKey {
    pub fn load(pem: &str) -> Result<Self, AppError> {
        let key = Ed25519KeyPair::from_pem(pem)?;
        Ok(Self(key))
    }
    pub fn generate_token(&self, user: User) -> Result<String, AppError> {
        let claim = Claims::with_custom_claims(user, Duration::from_secs(JWT_DURATION));
        let claim = claim
            .with_issuer("chat_server")
            .with_audience("chat_client");
        Ok(self.sign(claim)?)
    }
}

impl DecodingKey {
    pub fn load(pem: &str) -> Result<Self, AppError> {
        let key = Ed25519PublicKey::from_pem(pem)?;
        Ok(Self(key))
    }
    pub fn verify(&self, token: &str) -> Result<User, AppError> {
        let options = VerificationOptions {
            allowed_audiences: Some(HashSet::from(["chat_client".to_string()])),
            ..Default::default()
        };
        let claim = self.verify_token(token, Option::from(options))?;
        Ok(claim.custom)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::Local;

    #[tokio::test]
    async fn test_jwt() {
        let encoding_pem = include_str!("../../cert/private.pem");
        let decoding_pem = include_str!("../../cert/decoding.pem");
        let encoding_key = EncodingKey::load(encoding_pem).unwrap();
        let decoding_key = DecodingKey::load(decoding_pem).unwrap();
        let user = User {
            id: 1,
            name: "test".to_string(),
            phone: "test".to_string(),
            email: "test".to_string(),
            age: 1,
            password: "abb".to_string(),
            description: "123".to_string(),
            sex: 1,
            status: 1,
            create_at: Local::now(),
            update_at: Local::now(),
        };

        let token = encoding_key.generate_token(user).unwrap();
        println!("{token}");
        let user = decoding_key.verify(&token).unwrap();
        println!("{user:?}");
    }
}
